﻿using System;
using System.Collections.Generic;

namespace Game
{
    public class Board
    {
        public static BoardSize[] ValidBoardSizes
        {
            get { return sr_ValidBoardSizes; }
        } 

        private const char k_FirstSymbol = 'A';
        private const int k_MaxDim = 6;
        private const int k_MinDim = 4;
        private static readonly BoardSize[] sr_ValidBoardSizes;

        private int m_Rows, m_Cols;
        private Card[,] m_CardMatrix;

        static Board()
        {
            sr_ValidBoardSizes = new BoardSize[8];
            sr_ValidBoardSizes[0] = new BoardSize(4, 4);
            sr_ValidBoardSizes[1] = new BoardSize(4, 5);
            sr_ValidBoardSizes[2] = new BoardSize(4, 6);
            sr_ValidBoardSizes[3] = new BoardSize(5, 4);
            sr_ValidBoardSizes[4] = new BoardSize(5, 6);
            sr_ValidBoardSizes[5] = new BoardSize(6, 4);
            sr_ValidBoardSizes[6] = new BoardSize(6, 5);
            sr_ValidBoardSizes[7] = new BoardSize(6, 6);
        }

        private static bool isGoodDimensions(int i_Rows, int i_Cols)
        {
            return (i_Cols <= k_MaxDim) &&
                (i_Rows <= k_MaxDim) &&
                (i_Cols >= k_MinDim) &&
                (i_Rows >= k_MinDim) &&
                ((i_Rows % 2 == 0) || (i_Cols % 2 == 0));
        }

        public static bool Make(int i_Rows, int i_Cols, out Board o_Board)
        {
            o_Board = new Board(i_Rows, i_Cols);

            return isGoodDimensions(i_Rows, i_Cols);
        }

        private Board(int i_Rows, int i_Cols)
        {
            m_Rows = i_Rows;
            m_Cols = i_Cols;
            m_CardMatrix = new Card[m_Rows, m_Cols];

            resetCardMatrix();            
            resetBoardVisbility();
            scrambleCardMatrix();
        }

        private void resetBoardVisbility()
        {
            for (int i = 0; i < m_Rows; ++i)
            {
                for (int j = 0; j < m_Cols; ++j)
                {
                    CardEventArgs changeStatusArgs = new CardEventArgs();
                    changeStatusArgs.Status = Card.eCardStatus.Invisible;
                    m_CardMatrix[i, j].ChangeMyStatus(changeStatusArgs);
                }
            }
        }

        private void resetCardMatrix()
        {
            char symbol = k_FirstSymbol;
            int sequence = 2;

            for (int i = 0; i < m_Rows; ++i)
            {
                for (int j = 0; j < m_Cols; ++j)
                {
                    m_CardMatrix[i, j] = new Card(symbol);
                    sequence--;
                    if (sequence == 0)
                    {
                        sequence = 2;
                        symbol++;
                    }
                }
            }
        }

        private void scrambleCardMatrix()
        {
            System.Random rand = new System.Random();
            int randomIndex, currentIndex, maxIndex;
            int randomI, randomJ;
            maxIndex = m_Rows * m_Cols;

            for (int i = 0; i < m_Rows; ++i)
            {
                for (int j = 0; j < m_Cols; ++j)
                {
                    currentIndex = (m_Cols * i) + j;
                    randomIndex = rand.Next(currentIndex, maxIndex);

                    randomI = randomIndex / m_Cols;
                    randomJ = randomIndex % m_Cols;
                    
                    Card temp = m_CardMatrix[i, j];
                    m_CardMatrix[i, j] = m_CardMatrix[randomI, randomJ];
                    m_CardMatrix[randomI, randomJ] = temp;
                }
            }
        }
        
        public Card GetCard(Coordinate i_Coord)
        {
            if (!i_Coord.isValid(this))
            {
                throw new System.Exception("Exception: Invalide coordinate at Board.GetCard().");
            }

            return m_CardMatrix[i_Coord.i, i_Coord.j];
        }

        public bool IsLegalMove(Coordinate i_Coordinate)
        {
            Card card = this.GetCard(i_Coordinate);
         
            return card.Status == Card.eCardStatus.Invisible;
        }

        public int Cols
        {
            get { return m_Cols; }
        }

        public int Rows
        {
            get { return m_Rows; }
        }

        public void FlipCard(Coordinate i_Coord)
        {
            CardEventArgs changeStatusArgs = new CardEventArgs();
            changeStatusArgs.BoardPointer = this;
            changeStatusArgs.Coordinates = i_Coord;

            switch (m_CardMatrix[i_Coord.i, i_Coord.j].Status)
            {
                case Card.eCardStatus.Visible:
                    changeStatusArgs.Status = Card.eCardStatus.Invisible;
                    break;
                case Card.eCardStatus.Invisible:
                    changeStatusArgs.Status = Card.eCardStatus.Visible;
                    break;
                default:
                    throw new Exception("Cannot Flip.");
            }

            m_CardMatrix[i_Coord.i, i_Coord.j].ChangeMyStatus(changeStatusArgs);
        }

        public void BurnCard(Coordinate i_Coord)
        {
            CardEventArgs changeStatusArgs = new CardEventArgs();

            changeStatusArgs.BoardPointer = this;
            changeStatusArgs.Coordinates = i_Coord;
            changeStatusArgs.Status = Card.eCardStatus.Burned;

            m_CardMatrix[i_Coord.i, i_Coord.j].ChangeMyStatus(changeStatusArgs);
        }

        public bool IsCardVisible(Coordinate i_Coord)
        {
            return m_CardMatrix[i_Coord.i, i_Coord.j].Status != Card.eCardStatus.Invisible;
        }

        public bool IsCardBurned(Coordinate i_Coord)
        {
            return m_CardMatrix[i_Coord.i, i_Coord.j].Status == Card.eCardStatus.Burned;
        }

        public List<Coordinate> PoolLegalMoves()
        {
            List<Coordinate> legalCoordinatesPool = new List<Coordinate>();

            for (int i = 0; i < m_Rows; i++)
            {
                for (int j = 0; j < m_Cols; j++)
                {
                    Coordinate currentCoordinate = new Coordinate(i, j);

                    if (IsLegalMove(currentCoordinate))
                    {
                        legalCoordinatesPool.Add(currentCoordinate);
                    }
                }
            }

            return legalCoordinatesPool;
        }
 
        public class Coordinate
        {
            private int m_I, m_J;

            public Coordinate(int i_RowIndex, int i_ColIndex)
            {
                i = i_RowIndex;
                j = i_ColIndex;
            }

            public static bool Make(int i_RowIndex, int i_ColIndex, Board i_Board, out Coordinate o_Coord)
            {
                o_Coord = new Coordinate(i_RowIndex, i_ColIndex);

                return o_Coord.isValid(i_Board);
            }

            public static Coordinate MakeEmpty()
            {
                return new Coordinate(0, 0);
            }
            
            public bool isValid(Board i_Board)
            {
                return (m_I >= 0 && m_I <= i_Board.Rows - 1) && (m_J >= 0 && m_J <= i_Board.Cols - 1);
            }

            public int i
            {
                get { return m_I; }
                set { m_I = value; }
            }

            public int j
            {
                get { return m_J; }
                set { m_J = value; }
            }

            public bool Equals(Coordinate i_Other)
            {
                return this.i == i_Other.i && this.j == i_Other.j;
            }
        }

        public bool IsAllBoardBurned()
        {
            bool returnValue = true;

            foreach (Card card in m_CardMatrix)
            {
                returnValue = returnValue && (card.Status == Card.eCardStatus.Burned);
            }

            return returnValue;
        }

        public static int MaxDim
        {
            get { return k_MaxDim; }
        }

        public static int MinDim
        {
            get { return k_MinDim; }
        } 
    }

    public struct BoardSize
    {
        private int m_Rows;
        private int m_Cols;

        public BoardSize(int i_Rows, int i_Cols)
        {
            m_Rows = i_Rows;
            m_Cols = i_Cols;
        }

        public int Rows
        {
            get { return m_Rows; }
            set { m_Rows = value; }
        }

        public int Cols
        {
            get { return m_Cols; }
            set { m_Cols = value; }
        }
    }
}
